using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

using LumiSoft.Net.MIME.Header;

namespace LumiSoft.Net.MIME
{
    /// <summary>
    /// Represents a MIME entity. Defined in RFC 2045 2.4.
    /// </summary>
    public class MIME_Entity : IDisposable
    {
        private bool                       m_IsDisposed = false;
        private MIME_Entity                m_pParent    = null;
        private MIME_HeaderFieldCollection m_pHeader    = null;
        private MIME_Body                  m_pBody      = null;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public MIME_Entity()
        {
            m_pHeader = new MIME_HeaderFieldCollection();
        }

        #region method Dispose

        /// <summary>
        /// Cleans up any resources being used. This method is thread-safe.
        /// </summary>
        public void Dispose()
        {
            lock(this){
                if(m_IsDisposed){
                    return;
                }
                m_IsDisposed = true;

                m_pHeader = null;
                m_pParent = null;
            }
        }

        #endregion


        #region method ToFile

        /// <summary>
        /// Stores MIME entity to the specified file.
        /// </summary>
        /// <param name="file">File name with path where to store MIME entity.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>file</b> is null.</exception>
        /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception>
        public void ToFile(string file)
        {
            if(file == null){
                throw new ArgumentNullException("file");
            }
            if(file == ""){
                throw new ArgumentException("Argument 'file' value must be specified.");
            }

            using(FileStream fs = File.Create(file)){
                ToStream(fs);
            }
        }

        #endregion

        #region abstract method ToStream

        /// <summary>
        /// Store MIME enity to the specified stream.
        /// </summary>
        /// <param name="stream">Stream where to store MIME entity. Storing starts form stream current position.</param>
        /// <exception cref="ArgumentNullException">Is raised when <b>stream</b> is null.</exception>
        public void ToStream(Stream stream)
        {
            if(stream == null){
                throw new ArgumentNullException("stream");
            }

            this.Header.ToStream(stream,true);
            m_pBody.ToStream(stream);
        }

        #endregion


        #region method ParseInternal

        /// <summary>
        /// Parses MIME entity.
        /// </summary>
        /// <param name="stream">Stream from where to parse MIME entity.</param>
        /// <param name="owner">Specifies if entity is stream owner.</param>
        internal void ParseInternal(Stream stream,bool owner)
        {
            // Parse headers.
            m_pHeader.ParseFromStream(stream);

            // Content-Type not specified. RFC 2045 5.2. default = text/plain; charset=us-ascii.
            if(this.ContentType == null){
                m_pBody = new MIME_TextBody(this);
            }
            else if(this.ContentType.TypeWithSubype.ToLower() == "multipart/signed"){
                m_pBody = new MIME_MultipartSignedBody(this);
            }
            else if(this.ContentType.TypeWithSubype.ToLower() == "multipart/encrypted"){
                m_pBody = new MIME_MultipartEncryptedBody(this);
            }            
            else if(this.ContentType.Type.ToLower() == "multipart"){
                m_pBody = new MIME_MultipartBody(this);
            }
            else{
                m_pBody = new MIME_BinaryBody(this);
            }
            m_pBody.ParseFromStream(stream,owner);
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets if this object is disposed.
        /// </summary>
        public bool IsDisposed
        {
            get{ return m_IsDisposed; }
        }

        /// <summary>
        /// Gets the parent entity of this entity, returns null if this is the root entity.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public MIME_Entity Parent
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                return m_pParent; 
            }
        }

        /// <summary>
        /// Gets MIME entity header field collection.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public MIME_HeaderFieldCollection Header
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader; 
            }
        }
                                
        /// <summary>
        /// Gets or sets MIME version number. Value null means that header field does not exist. Normally this value is 1.0. Defined in RFC 2045 section 4.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>An indicator that this message is formatted according to the MIME
        /// standard, and an indication of which version of MIME is used.</remarks>
        public string MimeVersion
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("MIME-Version"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("MIME-Version",value); 
            }
        }

        /// <summary>
        /// Gets or sets content body part ID. Value null means that header field does not exist. Defined in RFC 2045 7.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Specifies a Unique ID for one MIME body part of the content of a message.</remarks>
        public string ContentID
        {
            get{ 
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-ID");
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-ID",value); 
            }
        }

        /// <summary>
        /// Gets or sets description of message body part. Value null means that header field does not exist. Defined in RFC 2045 8.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Description of a particular body part of a message; for example, a caption for an image body part.</remarks>
        public string ContentDescription
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Description"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Description",value); 
            }
        }

        /// <summary>
        /// Gets or sets content transfer encoding. Value null means that header field does not exist. Defined in RFC 2045 6.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Coding method used in a MIME message body part.</remarks>
        public string ContentTransferEncoding
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Transfer-Encoding"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Transfer-Encoding",value); 
            }
        }

        /// <summary>
        /// Gets or sets MIME content type. Value null means that header field does not exist. Defined in RFC 2045 5.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        public MIME_t_ContentType ContentType
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return (MIME_t_ContentType)m_pHeader.GetFirstValue("Content-Type"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Type",value); 
            }
        }

        /// <summary>
        /// Gets or sets base to be used for resolving relative URIs within this content part. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Base to be used for resolving relative URIs within this content part. See also Content-Location.</remarks>
        public string ContentBase
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Base"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Base",value); 
            }
        }

        /// <summary>
        /// Gets or sets URI for retrieving a body part. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>URI using which the content of this body-part part was retrieved,
        /// might be retrievable, or which otherwise gives a globally unique identification of the content.</remarks>
        public string ContentLocation
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Location"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Location",value); 
            }
        }

        /// <summary>
        /// Gets or sets content features of a MIME body part. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>The 'Content-features:' header can be used to annotate a MIME body part with a media feature expression, 
        /// to indicate features of the body part content. See also RFC 2533, RFC 2506, and RFC 2045.</remarks>
        public string Contentfeatures
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-features"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }

                m_pHeader.SetFirstValue("Content-features",value); 
            }
        }

        /// <summary>
        /// Gets or sets content disposition. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Indicates whether a MIME body part is to be shown inline or is an attachment; can also indicate a 
        /// suggested filename for use when saving an attachment to a file.</remarks>
        public MIME_t_ContentDisposition ContentDisposition
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return (MIME_t_ContentDisposition)m_pHeader.GetFirstValue("Content-Disposition"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Disposition",value); 
            }
        }

        /// <summary>
        /// Gets or sets language of message content. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Can include a code for the natural language used in a message; e.g., 'en' for English. 
        /// Can also contain a list of languages for a message containing more than one language.</remarks>
        public string ContentLanguage
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Language"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Language",value); 
            }
        }

        /// <summary>
        /// Gets or sets message alternative content. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Information about the media features of alternative content formats available for the current message.</remarks>
        public string ContentAlternative
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Alternative"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Alternative",value); 
            }
        }
        
        /// <summary>
        /// Gets or sets content MD5 checksum. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Checksum of content to ensure that it has not been modified.</remarks>
        public string ContentMD5
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-MD5"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-MD5",value); 
            }
        }
        
        /// <summary>
        /// Gets or sets time duration of content. Value null means that header field does not exist.
        /// </summary>
        /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and this property is accessed.</exception>
        /// <remarks>Time duration of body part content, in seconds (e.g., for audio message).</remarks>
        public string ContentDuration
        {
            get{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                return m_pHeader.GetFirstValueString("Content-Duration"); 
            }

            set{
                if(m_IsDisposed){
                    throw new ObjectDisposedException(this.GetType().Name);
                }
                
                m_pHeader.SetFirstValue("Content-Duration",value); 
            }
        }

        /// <summary>
        /// Gets entity body.
        /// </summary>
        public MIME_Body Body
        {
            get{ return m_pBody; }
        }
                        
        #endregion

    }
}
